package model.entity.brain;

import java.util.HashSet;
import java.util.Random;


import model.entity.*;
import model.map.Direction;
import model.map.HexLocationProjection;
import model.map.Location;
import model.map.World;
import model.handler.MovementHandler;
import model.Executable;
import model.GameClock;
import utilities.*;

public class AggressiveBrain extends Brain implements Archiveable {
	private Location avatarLoc;
	private BasicEntity npc;
	
	
	public AggressiveBrain(Avatar av, BasicEntity ent){
		avatarLoc = av.getLocation();
		npc = ent;
		
		GameClock.getInstance().schedule(40, new Executable() {
			public void execute() { start(); }
		});
		
	}
	
	public AggressiveBrain() {
		// TODO Auto-generated constructor stub
	}

	public void start(){
		MovementHandler mh = MovementHandler.getInstance();
		mh.moveEntity(npc, getNextMove());
		
		final GameClock c = GameClock.getInstance();
		
		this.ex = new Executable() {
			public void execute() {
				MovementHandler mh = MovementHandler.getInstance();
				mh.moveEntity(npc, getNextMove());
			}
		}; 
		
		c.scheduleAtFixedRate(10, this.ex);
	}
	
	public void cancelBrainFunctioning() {
		GameClock.getInstance().remove( this.ex );
	}
	
	/**
	 * move in the direction of the Avatar or if he is too far, wander
	 * @return
	 */
	private Direction getNextMove(){
		// if the Avatar is close chase
		if (detectIfInRange()){
			// move in the avatars direction
			return chaseDir();
		}
		// if the avatar is too far, wander
		else {
			return getWanderDir();
		}
	}
	
	/**
	 * gets a random direction for wandering
	 * @return
	 */
	private Direction getWanderDir(){
		Direction dir = Direction.SW;
		Random randGen = new Random();
		int numPadDirection = 5;
		// keep randomly choosing numbers until it is 1,2,3 or 7,8,9 for movingdirections (related to keypad)
		while(numPadDirection == 4 || numPadDirection == 5 || numPadDirection == 6){
			numPadDirection = randGen.nextInt(10);
		}
		
		// convert the number chosen to a direction
		switch(numPadDirection){
			case 1: dir = Direction.SW; break;
			case 2: dir = Direction.S; break;
			case 3: dir = Direction.SE; break;
			case 7: dir = Direction.NW; break;
			case 8: dir = Direction.N; break;
			case 9: dir = Direction.NE; break;
		}
		
		return dir;
	}
	
	/**
	 * used to see if the Avatar is close enough to be chased
	 * @return
	 */
	private boolean detectIfInRange(){
		HexLocationProjection hlp = new HexLocationProjection();
		Direction dir = Direction.SW;
		Location tempLoc;
		
		// this is the surrounding locations
		HashSet <Location>locSet = new HashSet <Location>();
		
		locSet.add(npc.getLocation());

		// should be able to see a 5 tile radius
		for(int radius = 1; radius < 5; radius++){
			// hashset leads to concurrent issues to array is used
			int size = locSet.size();
			Object[] locatSet = locSet.toArray();
			// add each location surrounding the current one to the set
			for(int ct=0; ct<size; ct++){
				// check each surrounding direction
				for(int i=0; i<10; i++){
					// skip illegal directions 4,5,6
					if(i == 4)
						i = 7;
					// convert the number chosen to a direction
					switch(i){
						case 1: dir = Direction.SW; break;
						case 2: dir = Direction.S; break;
						case 3: dir = Direction.SE; break;
						case 7: dir = Direction.NW; break;
						case 8: dir = Direction.N; break;
						case 9: dir = Direction.NE; break;
					}
					//currloc holds where we are, temploc holds where we would move
					Location currLoc = (Location) locatSet[ct];
					tempLoc = hlp.projectLocation(currLoc, dir);
					if( !locSet.contains(tempLoc) ){
						locSet.add(tempLoc);
						locatSet = locSet.toArray();
					}
					avatarLoc = World.getInstance().getPlayer().getLocation();

					// if the avatar is in the influence radius we are building return true
					if(hlp.projectLocation(tempLoc, dir).equals(avatarLoc)){
						return true;
					}
	
				}
			}
		}
		
		// if we didnt find the avatar return false
		return false;
	}
	
	/**
	 * find the direction to move in order to chase the avatar
	 * @return
	 */
	private Direction chaseDir(){
		HexLocationProjection hlp = new HexLocationProjection();
		Location tempLoc;
		Direction bestDir = Direction.SW;
		Direction dir = Direction.SW;
		int minDist = 999;
		
		// cycle through each direction
		for(int i=0; i < 10; i ++){
			// skip illegal directions 4,5,6,
			if( i == 4)
				i = 7;
			
			// convert the number chosen to a direction
			switch(i){
				case 1: dir = Direction.SW; break;
				case 2: dir = Direction.S; break;
				case 3: dir = Direction.SE; break;
				case 7: dir = Direction.NW; break;
				case 8: dir = Direction.N; break;
				case 9: dir = Direction.NE; break;
			}
			
			// find the next block in that direction
			tempLoc = hlp.projectLocation(this.npc.getLocation(), dir);
			
			// only store direction with shortest distance to the avatar
			if(distanceCalculator(tempLoc, avatarLoc) < minDist){
				bestDir = dir;
				minDist = distanceCalculator(tempLoc, avatarLoc);
			}
		}
		return bestDir;
	}
	
	private int distanceCalculator(Location loc1, Location loc2){
		int xdist = loc1.getX() - loc2.getX();
		int ydist = loc1.getY() - loc2.getY();
		
		// keep distance values positive
		if(xdist < 0){
			xdist = -xdist;
		}
		if(ydist < 0){
			ydist = -ydist;
		}
		return xdist+ydist;
	}
	
	public Archive save() {
		Archive archive = super.save();
		
//		archive.putClassName("avatarLoc", this.avatarLoc.getClass().getName());
//		archive.putOneRelationship("avatarLoc", this.avatarLoc);
		
		return archive;
	}
	
	public void load(Archive archive) {
		super.load(archive);
		
//		this.avatarLoc = (Location)ModelFactory.getInstance().createInstanceWithClassName(archive.getClassName("avatarLoc"));
//		this.avatarLoc.load(archive.getOneRelationship("avatarLoc"));
	}
}